<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
  <meta name="baidu-site-verification" content="code-UjJKh3jtOo">
  <link rel="stylesheet" href="//unpkg.com/@highlightjs/cdn-assets@11.5.0/styles/atom-one-dark.min.css">
  <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
  <title>Tit1e</title>
  <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-2767111597631391" crossorigin="anonymous"></script>
  
<link rel="stylesheet" href="/css/style.css">

<meta name="generator" content="Hexo 6.3.0"><link rel="alternate" href="/atom.xml" title="Tit1e" type="application/atom+xml">
</head>

<body class="dark">
    <div class="container">
    <header class="header">
  <div class="title">
      <a href="/" class="logo">Tit1e</a>
      <div class="btn-dark"></div>
      <script>
        let bodyClx = document.body.classList;
        let btnDark = document.querySelector('.btn-dark');
        let sysDark = window.matchMedia('(prefers-color-scheme: dark)');
        let darkVal = localStorage.getItem('dark');

        let setDark = (isDark) => {
        bodyClx[isDark ? 'add' : 'remove']('dark');
        localStorage.setItem('dark', isDark ? 'yes' : 'no');
        };

        setDark(darkVal ? darkVal === 'yes' : sysDark.matches);
        requestAnimationFrame(() => bodyClx.remove('not-ready'));

        btnDark.addEventListener('click', () => setDark(!bodyClx.contains('dark')));
        sysDark.addEventListener('change', (event) => setDark(event.matches));
      </script>
  </div>
    <ul class="menu">
        
        
        
        
        <li class="menu-item ">
            <a href="/" class="menu-item-link">Home</a>
        </li>
        
        
        <li class="menu-item ">
            <a href="/archives/" class="menu-item-link">Archives</a>
        </li>
        
        
        <li class="menu-item ">
            <a href="/atom.xml" class="menu-item-link">RSS</a>
        </li>
        
        
        <li class="menu-item ">
            <a target="_blank" rel="noopener" href="https://pixeltimer.art" class="menu-item-link">Pixel Timer</a>
        </li>
        
    </ul>
    <div class="search-box">
        <input type="search" id="search" placeholder="搜索">
    </div>
</header>
    <article class="post">
  <div class="post-meta">
    <span class="post-time">3 / 27, 2019</span>
  </div>
  <div class="post-title">
    <h1 class="title">编写一个全局 SVG 图标组件</h1>
  </div>
  
  <ul class="post-tags">
    
    <li class="tag-item">
      #学习
    </li>
    
    <li class="tag-item">
      #vue
    </li>
    
  </ul>
  
  <div class="post-content">
    <p>在现在网站开发中， icon 是必不可少的元素，它可以使页面更加美观，内容更加丰富，某些场景也能更加直观地表达含义。icon 也分好多种类型，以前是使用图片，后来有了字体图标，SVG。由于占用空间大以及使用不方便（比如不同终端需要不同尺寸的图片），用图片做图标的方式已经渐渐被淘汰了，取而代之的是字体图标以及 SVG，这两种都是矢量图形，光放大不失真这个特点就很让人心动，并且体积还比图片小。</p>
<p>字体图标顾名思义就是这个图标就是一个字体，只是它显示的是个图标。因此 CSS 能对字体设置的属性，对字体图标也同样适用。</p>
<p>缺点的话可能只是想使用这个图标库的某几个图标，但是却必须把整个图标库引入，造成了资源浪费。不过现在这个问题在 <a target="_blank" rel="noopener" href="https://www.iconfont.cn/">阿里妈妈</a> 图标管理平台得到了很好的解决，可以自己定义需要用到的图标，然后系统会帮你生成一个图标库，可以在线引入，也可以下载到本地，可以说是造福了广大前端工程师和设计师。</p>
<p>虽然阿里的这个平台已经做得很好了，但是就我而言，在实际项目开发中还是遇到了不方便的问题：</p>
<ul>
<li>之前没考虑周全，或者后来来了新的需求，需要增加新图标，而要更新图标库是需要账号的，更新一个图标可能会涉及到根很多人沟通。</li>
<li>更新完图标库后还需去更新在线引用地址或更新本地文件。</li>
</ul>
<p>SVG，以下是 <a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E5%8F%AF%E7%B8%AE%E6%94%BE%E5%90%91%E9%87%8F%E5%9C%96%E5%BD%A2">维基百科</a> 对它的描述：</p>
<blockquote>
<p><strong>可缩放矢量图形</strong>（英语：<strong>Scalable Vector Graphics</strong>，<strong>SVG</strong>）是一种基于 <a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E5%8F%AF%E6%89%A9%E5%B1%95%E6%A0%87%E8%AE%B0%E8%AF%AD%E8%A8%80">可扩展标记语言</a> （<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/XML">XML</a>），用于描述二维 <a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E7%9F%A2%E9%87%8F%E5%9B%BE%E5%BD%A2">矢量图形</a> 的图形格式。SVG由 <a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/W3C">W3C</a> 制定，是一个 <a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E5%BC%80%E6%94%BE%E6%A0%87%E5%87%86">开放标准</a>。</p>
</blockquote>
<p>SVG 其实是 XML格式的文件，因此你可以直接使用编辑器打开 .svg 文件，并且可对它进行编辑。它的优点在于：</p>
<ul>
<li>一个图标就是一个文件，灵活性高。</li>
<li>不同区域可以使用不同颜色，也就是说可以用它绘制彩色图标。</li>
<li>矢量图形，放大不失真。</li>
</ul>
<p>SVG 文件的使用可以分为很多种，你可以把它当作图片直接在 <code>img</code> 标签或 CSS 的 <code>background-image</code> 属性中使用，也可使用 <code>svg</code> 标签中使用。但是这样使用会很麻烦，避免不了一串很长的地址，如果只要写一个标签，并传入一个图标名字就可直接使用，那就完美了。</p>
<p>我在  <a target="_blank" rel="noopener" href="https://github.com/PanJiaChen/vue-element-admin">vue-element-admin</a> 项目中发现了这种使用方式，因此把它提取了出来。</p>
<p>首先参考 <a href="https://evolly.one/2019/03/12/53.vue-cli3-use-svg-sprite-loader/">在 Vue CLI 3 中配置 svg-sprite-loader</a> 在 webpack 添加 SVG 的 loader。并建好相应的目录，文章中我的目录建在 <code>src/icons</code>。</p>
<p>在 <code>icons</code> 文件夹下新建 <code>svg</code> 文件夹用来存放图标，在同级目录下新建 <code>index.js</code>。</p>
<p>然后我们去编写组件，名字就命名为 <code>SvgIcon</code>。</p>
<pre><code class="vue">// SvgIcon.vue
&lt;template&gt;
  &lt;svg :class=&quot;svgClass&quot; aria-hidden=&quot;true&quot; v-on=&quot;$listeners&quot;&gt;
    &lt;use :xlink:href=&quot;iconName&quot; /&gt;
  &lt;/svg&gt;
&lt;/template&gt;

&lt;script&gt;
export default &#123;
  name: &#39;SvgIcon&#39;,
  props: &#123;
    iconClass: &#123;
      type: String,
      required: true
    &#125;,
    className: &#123;
      type: String,
      default: &#39;&#39;
    &#125;
  &#125;,
  computed: &#123;
    iconName() &#123;
      return `#icon-$&#123;this.iconClass&#125;`
    &#125;,
    svgClass() &#123;
      if (this.className) &#123;
        return &#39;svg-icon &#39; + this.className
      &#125; else &#123;
        return &#39;svg-icon&#39;
      &#125;
    &#125;
  &#125;
&#125;
&lt;/script&gt;

&lt;style scoped&gt;
.svg-icon &#123;
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  fill: currentColor;
  overflow: hidden;
&#125;
&lt;/style&gt;
</code></pre>
<p>然后继续编写 <code>src/icons/index.js</code></p>
<pre><code class="javascript">// src/icons/index.js

import Vue from &#39;vue&#39;
import SvgIcon from &#39;@/components/SvgIcon&#39;// svg组件

// 注册为全局组件
Vue.component(&#39;svg-icon&#39;, SvgIcon)

const req = require.context(&#39;./svg&#39;, false, /\.svg$/)
const requireAll = requireContext =&gt; requireContext.keys().map(requireContext)
requireAll(req)
</code></pre>
<p>最后，在 <code>main.js</code> 中引入</p>
<pre><code class="javascript">// main.js

import &#39;@/icons&#39;
</code></pre>
<p>这样你就可以在系统任何地方使用 <code>&lt;svg-icon icon-name=&quot;svg名称&quot; /&gt;</code> 引入图标了，当你想增加图标时，只要往 <code>src/icons/svg</code> 中添加图标就可以了。</p>
<p>结合 <a href="https://evolly.one/2019/03/26/54.vuecl3-npm-publish/">使用 Vue CLI3 编写组件库并发布至 npm</a>，你就可以拥有一个属于自己的图标库。你只要维护这个图标库，然后在其他项目中安装与更新它就可以了。</p>

  </div>
  <div class="post-near">
    
    <a
      class="post-near-prev"
      href="/p/55ce0e11.html"
      title="element-ui 树形控件使用右键菜单"
    >
      <span class="arrow">←</span>
      <span class="post-near-title">
        element-ui 树形控件使用右键菜单
      </span>
    </a>
    
    
    <a
      class="post-near-next"
      href="/p/2a9d6337.html"
      title="网页打印指定区域内容"
    >
      <span class="post-near-title">
        网页打印指定区域内容
      </span>
      <span class="arrow">→</span>
    </a>
    
  </div>
</article>

    <footer class="footer">
  <p>本博客采用 <a class="license-link" rel="license noopener" target="_blank" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh">署名-非商业性使用-相同方式共享 4.0 国际</a>协议进行许可</p>
  <p>
    <span>Thanks For <a target="_blank" rel="noopener" href="https://github.com/nanxiaobei/hugo-paper" taget="_blank">hugo-paper</a></span>
    <span style="margin-left: 10px;">&copy; 2016-2025 by Tit1e</span>
  </p>
</footer>
    </div>
    <script src="//unpkg.com/@highlightjs/cdn-assets@11.5.0/highlight.min.js"></script>
<script>
  hljs.initHighlightingOnLoad();
  var _hmt = _hmt || [];
    (function() {
    var hm = document.createElement("script");
    hm.src = "https://hm.baidu.com/hm.js?6349d3bc54baea0b04a65145c5e3b799";
    var s = document.getElementsByTagName("script")[0];
    s.parentNode.insertBefore(hm, s);
    })();
    const block = document.querySelectorAll('.post-content')[0]
    block.addEventListener('click', e => {
        const {nodeName, src} = e.target
        if(nodeName !== 'IMG') return
        document.body.classList.add('overflow-hidden')
        const div = document.createElement('div')
        div.id = 'global-cover'
        div.title = '点击关闭'
        const a = document.createElement('a')
        a.href = src
        a.target="_blank"
        a.title = '点击打开原图'
        const img = document.createElement('img')
        img.className = 'show-pic'
        img.src = src
        a.appendChild(img)
        div.appendChild(a)
        document.body.appendChild(div)
    })
    document.body.addEventListener('click', e => {
        if(e.target.id === 'global-cover'){
            document.body.classList.remove('overflow-hidden')
            document.body.removeChild(e.target)
        }
    })
</script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-7M04VN3JZ4"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'G-7M04VN3JZ4');
</script>

<script src="/js/search.js"></script>


</body>
</html>